﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

using Acid.OpenGLES;
using Acid.Math;
using System.Runtime.InteropServices;
namespace Acid.Graphics
{


    public unsafe class Device : IDisposable
    {
        private Control           control;
        private EGLDisplay        display;
        private EGLSurface        surface;
        private EGLContext        context;

        private Transform         transform;
        private State             state;
        private ViewPort          viewPort;

        private VertexBuffer      vertexBuffer;
        private IndexBuffer       indexBuffer;
        private List<Light>       lights;
        private List<ITexture>    textures;

 
        public Device(Control control)
        {
            this.control    = control;

            this.Initialize();

            this.transform  = new Transform();

            this.viewPort   = new ViewPort();

            this.state      = new State();

            this.lights     = new List<Light>();

            this.textures   = new List<ITexture>();
        }

        private void Initialize()
        {
            this.display = egl.GetDisplay(EGLNativeDisplayType.Default);
            
            int major, minor;
            
            egl.Initialize(display, out major, out minor);

            EGLConfig[] configs = new EGLConfig[10];

            int[] attribList = new int[] 
            { 
                egl.EGL_RED_SIZE,   5, 
                egl.EGL_GREEN_SIZE, 6, 
                egl.EGL_BLUE_SIZE,  5, 
                egl.EGL_DEPTH_SIZE, 16 , 
                egl.EGL_SURFACE_TYPE, egl.EGL_WINDOW_BIT,
                egl.EGL_STENCIL_SIZE, egl.EGL_DONT_CARE,
                egl.EGL_NONE, egl.EGL_NONE 
            };

            int numConfig;

            if (!egl.ChooseConfig(display, attribList, configs, configs.Length, out numConfig) || numConfig < 1)
            {
                throw new InvalidOperationException("Unable to choose config.");
            }

            EGLConfig config = configs[0];
            
            this.surface = egl.CreateWindowSurface(display, config, this.control.Handle, null);
            
            this.context = egl.CreateContext(display, config, EGLContext.None, null);

            egl.MakeCurrent(this.display, this.surface, this.surface, this.context);

            gl.ShadeModel(gl.GL_SMOOTH);

            gl.ClearColor(0.0f, 0.0f, 0.0f, 0.5f);
            
            gl.ClearDepthf(1.0f);

            gl.Enable(gl.GL_DEPTH_TEST);

            gl.DepthFunc(gl.GL_LEQUAL);

            gl.Hint(gl.GL_PERSPECTIVE_CORRECTION_HINT, gl.GL_NICEST);
        }

        #region Properties
        /// <summary>
        /// Contains Fixed Function Transformation Matrices
        /// </summary>
        public Transform         Transform          
        {
            get { return this.transform; }
        }
        /// <summary>
        /// The OpenGL ViewPort
        /// </summary>
        public ViewPort          ViewPort           
        {
            get
            {
                return this.viewPort; 
            }
            set
            {
                this.viewPort = value;

                gl.Viewport(this.viewPort.X, this.viewPort.Y, this.viewPort.Width, this.viewPort.Height);
            }
        }
        /// <summary>
        /// Gets and Sets the Currently Bound VertexBuffer
        /// </summary>
        public VertexBuffer      VertexBuffer         
        {
            get { return this.vertexBuffer; }
            set { this.vertexBuffer = value; }
        }
        /// <summary>
        /// Gets and Sets the Currently Bound IndexBuffer
        /// </summary>
        public IndexBuffer        IndexBuffer 
        {
            get { return this.indexBuffer; }
            set { this.indexBuffer = value; }
        }
        /// <summary>
        /// The Lights
        /// </summary>
        public List<Light> Lights
        {
            get { return this.lights; }
        }

        public List<ITexture> Textures
        {
            get { return this.textures; }
        }
        #endregion

        #region Pipeline

        private void EnableVertexDeclaration()
        {
            #region Enable Array State

            foreach (VertexElement element in this.vertexBuffer.VertexDeclaration.Elements)
            {
                switch (element.ElementBinding)
                {
                    case ElementBinding.Vertex:
                        gl.EnableClientState(gl.GL_VERTEX_ARRAY);
                        break;
                    case ElementBinding.Normal:
                        gl.EnableClientState(gl.GL_NORMAL_ARRAY);
                        break;
                    case ElementBinding.Color:
                        gl.EnableClientState(gl.GL_COLOR_ARRAY);
                        break;
                    case ElementBinding.TexCoord0:
                        gl.ClientActiveTexture(gl.GL_TEXTURE0);
                        gl.EnableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord1:
                        gl.ClientActiveTexture(gl.GL_TEXTURE1);
                        gl.EnableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord2:
                        gl.ClientActiveTexture(gl.GL_TEXTURE2);
                        gl.EnableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord3:
                        gl.ClientActiveTexture(gl.GL_TEXTURE3);
                        gl.EnableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord4:
                        gl.ClientActiveTexture(gl.GL_TEXTURE4);
                        gl.EnableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord5:
                        gl.ClientActiveTexture(gl.GL_TEXTURE5);
                        gl.EnableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord6:
                        gl.ClientActiveTexture(gl.GL_TEXTURE6);
                        gl.EnableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord7:
                        gl.ClientActiveTexture(gl.GL_TEXTURE7);
                        gl.EnableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord8:
                        gl.ClientActiveTexture(gl.GL_TEXTURE8);
                        gl.EnableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord9:
                        gl.ClientActiveTexture(gl.GL_TEXTURE9);
                        gl.EnableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord10:
                        gl.ClientActiveTexture(gl.GL_TEXTURE10);
                        gl.EnableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord11:
                        gl.ClientActiveTexture(gl.GL_TEXTURE11);
                        gl.EnableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord12:
                        gl.ClientActiveTexture(gl.GL_TEXTURE12);
                        gl.EnableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord13:
                        gl.ClientActiveTexture(gl.GL_TEXTURE13);
                        gl.EnableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord14:
                        gl.ClientActiveTexture(gl.GL_TEXTURE14);
                        gl.EnableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord15:
                        gl.ClientActiveTexture(gl.GL_TEXTURE15);
                        gl.EnableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                }
            }


            #endregion

            #region Enable Vertex Buffers
            
            int count = 0;

            foreach (VertexElement element in this.vertexBuffer.VertexDeclaration.Elements)
            {
                float [] data = this.vertexBuffer.Streams[count].ToArray(); count++;

                GCHandle gch = GCHandle.Alloc(data, GCHandleType.Pinned);

                switch (element.ElementBinding)
                {
                    case ElementBinding.Vertex:
                        gl.VertexPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.Normal:
                        gl.NormalPointer((uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.Color:
                        gl.ColorPointer((int)element.TypeCount,(uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.TexCoord0:
                        gl.ClientActiveTexture(gl.GL_TEXTURE0);
                        gl.TexCoordPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.TexCoord1:
                        gl.ClientActiveTexture(gl.GL_TEXTURE1);
                        gl.TexCoordPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.TexCoord2:
                        gl.ClientActiveTexture(gl.GL_TEXTURE2);
                        gl.TexCoordPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.TexCoord3:
                        gl.ClientActiveTexture(gl.GL_TEXTURE3);
                        gl.TexCoordPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.TexCoord4:
                        gl.ClientActiveTexture(gl.GL_TEXTURE4);
                        gl.TexCoordPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.TexCoord5:
                        gl.ClientActiveTexture(gl.GL_TEXTURE5);
                        gl.TexCoordPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.TexCoord6:
                        gl.ClientActiveTexture(gl.GL_TEXTURE6);
                        gl.TexCoordPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.TexCoord7:
                        gl.ClientActiveTexture(gl.GL_TEXTURE7);
                        gl.TexCoordPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.TexCoord8:
                        gl.ClientActiveTexture(gl.GL_TEXTURE8);
                        gl.TexCoordPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.TexCoord9:
                        gl.ClientActiveTexture(gl.GL_TEXTURE9);
                        gl.TexCoordPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.TexCoord10:
                        gl.ClientActiveTexture(gl.GL_TEXTURE10);
                        gl.TexCoordPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.TexCoord11:
                        gl.ClientActiveTexture(gl.GL_TEXTURE11);
                        gl.TexCoordPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.TexCoord12:
                        gl.ClientActiveTexture(gl.GL_TEXTURE12);
                        gl.TexCoordPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.TexCoord13:
                        gl.ClientActiveTexture(gl.GL_TEXTURE13);
                        gl.TexCoordPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.TexCoord14:
                        gl.ClientActiveTexture(gl.GL_TEXTURE14);
                        gl.TexCoordPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                    case ElementBinding.TexCoord15:
                        gl.ClientActiveTexture(gl.GL_TEXTURE15);
                        gl.TexCoordPointer((int)element.TypeCount, (uint)element.ElementType, 0, gch.AddrOfPinnedObject());
                        break;
                }

                gch.Free();
            }
            #endregion 
        }

        private void DisableVertexDeclaration()
        {
            foreach (VertexElement element in this.vertexBuffer.VertexDeclaration.Elements)
            {
                switch (element.ElementBinding)
                {
                    case ElementBinding.Vertex:
                        gl.EnableClientState(gl.GL_VERTEX_ARRAY);
                        break;
                    case ElementBinding.Normal:
                        gl.EnableClientState(gl.GL_NORMAL_ARRAY);
                        break;
                    case ElementBinding.Color:
                        gl.EnableClientState(gl.GL_COLOR_ARRAY);
                        break;
                    case ElementBinding.TexCoord0:
                        gl.ClientActiveTexture(gl.GL_TEXTURE0);
                        gl.DisableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord1:
                        gl.ClientActiveTexture(gl.GL_TEXTURE1);
                        gl.DisableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord2:
                        gl.ClientActiveTexture(gl.GL_TEXTURE2);
                        gl.DisableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord3:
                        gl.ClientActiveTexture(gl.GL_TEXTURE3);
                        gl.DisableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord4:
                        gl.ClientActiveTexture(gl.GL_TEXTURE4);
                        gl.DisableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord5:
                        gl.ClientActiveTexture(gl.GL_TEXTURE5);
                        gl.DisableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord6:
                        gl.ClientActiveTexture(gl.GL_TEXTURE6);
                        gl.DisableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord7:
                        gl.ClientActiveTexture(gl.GL_TEXTURE7);
                        gl.DisableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord8:
                        gl.ClientActiveTexture(gl.GL_TEXTURE8);
                        gl.DisableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord9:
                        gl.ClientActiveTexture(gl.GL_TEXTURE9);
                        gl.DisableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord10:
                        gl.ClientActiveTexture(gl.GL_TEXTURE10);
                        gl.DisableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord11:
                        gl.ClientActiveTexture(gl.GL_TEXTURE11);
                        gl.DisableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord12:
                        gl.ClientActiveTexture(gl.GL_TEXTURE12);
                        gl.DisableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord13:
                        gl.ClientActiveTexture(gl.GL_TEXTURE13);
                        gl.DisableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord14:
                        gl.ClientActiveTexture(gl.GL_TEXTURE14);
                        gl.DisableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                    case ElementBinding.TexCoord15:
                        gl.ClientActiveTexture(gl.GL_TEXTURE15);
                        gl.DisableClientState(gl.GL_TEXTURE_COORD_ARRAY);
                        break;
                }
            }
        }

        private void EnableLights()
        {
            for (uint i = 0; i < this.lights.Count; i++)
            {
                if (this.lights[(int)i].Enabled)
                {
                    GCHandle gchPosition = GCHandle.Alloc(this.lights[(int)i].Position, GCHandleType.Pinned);
                    GCHandle gchAmbient = GCHandle.Alloc(this.lights[(int)i].Ambient, GCHandleType.Pinned);
                    GCHandle gchDiffuse = GCHandle.Alloc(this.lights[(int)i].Diffuse, GCHandleType.Pinned);
                    GCHandle gchSpecular = GCHandle.Alloc(this.lights[(int)i].Specular, GCHandleType.Pinned);

                    gl.Lightfv(gl.GL_LIGHT0, gl.GL_SPECULAR, (float*)gchSpecular.AddrOfPinnedObject());
                    gl.Lightfv(gl.GL_LIGHT0, gl.GL_AMBIENT, (float*)gchAmbient.AddrOfPinnedObject());
                    gl.Lightfv(gl.GL_LIGHT0, gl.GL_DIFFUSE, (float*)gchDiffuse.AddrOfPinnedObject());
                    gl.Lightfv(gl.GL_LIGHT0, gl.GL_POSITION, (float*)gchPosition.AddrOfPinnedObject());
                    gl.Enable(gl.GL_LIGHT0 + i);
                }
            }
        }

        private void DisableLights()
        {
            for (uint i = 0; i < this.lights.Count; i++)
            {
                if (this.lights[(int)i].Enabled)
                {
                    gl.Disable(gl.GL_LIGHT0 + i);
                }
            }
        }

        private void EnableTextures()
        {
            for (uint i = 0; i < this.textures.Count; i++)
            {
                ITexture itexture = this.textures[(int)i];
                Texture2D texture = (Texture2D)itexture;
                gl.ActiveTexture(gl.GL_TEXTURE0 + i);
                gl.BindTexture  (gl.GL_TEXTURE_2D, texture.Handle);
                gl.Enable       (gl.GL_TEXTURE_2D);
                 
            }
        }

        private void DisableTextures()
        {
            for (uint i = 0; i < this.textures.Count; i++)
            {
                ITexture itexture = this.textures[(int)i];

                gl.ActiveTexture(gl.GL_TEXTURE0 + i);
                gl.BindTexture  (gl.GL_TEXTURE_2D, 0);
                gl.Disable      (gl.GL_TEXTURE_2D);
               
                itexture = null;
            }
        }

        #endregion


        #region Methods

        /// <summary>
        /// Clears the Device Buffer
        /// </summary>
        /// <param name="r"></param>
        /// <param name="g"></param>
        /// <param name="b"></param>
        /// <param name="a"></param>
        public void      Clear  (float r, float g, float b, float a)
        {
            gl.ClearColor(r, g, b, a);

            gl.Clear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT);
        }
        
        /// <summary>
        /// Renders Geometry based on the currently bound IndexData, VertexData and VertexDeclaration
        /// </summary>
        /// <param name="topology"></param>
        public void      Draw   (Topology topology)
        {
            if (this.vertexBuffer == null || this.indexBuffer == null) return;

            if (this.lights.Count > 0) gl.Enable(gl.GL_LIGHTING);

            this.EnableTextures();

            this.EnableLights();

            this.EnableVertexDeclaration();

            GCHandle gch = GCHandle.Alloc(this.IndexBuffer.Indices.ToArray(), GCHandleType.Pinned);

            gl.DrawElements((uint)topology, this.indexBuffer.Indices.Count, (uint)gl.GL_UNSIGNED_SHORT, gch.AddrOfPinnedObject());

            gch.Free();

            this.DisableVertexDeclaration();

            this.DisableLights();

            this.DisableTextures();

            if (this.lights.Count > 0) gl.Disable(gl.GL_LIGHTING);

            gl.Flush();
        }
        
        
        /// <summary>
        /// Presents the scene to the display buffer.
        /// </summary>
        public void      Present()
        {
            egl.SwapBuffers(this.display, this.surface);
        }

        #endregion


        #region IDisposable Members

        private bool disposed = false;

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (!this.disposed)
                {
                    if (!egl.DestroySurface(this.display, this.surface))  throw new Exception("Error while destroying surface.");
                    if (!egl.DestroyContext(this.display, this.context))  throw new Exception("Error while destroying context.");
                    if (!egl.Terminate(this.display))                     throw new Exception("Error while terminating display.");
                }
                this.disposed = true;
            }
        }

        public void Dispose()
        {
            this.Dispose(true);
        }

        ~Device()
        {
            this.Dispose(false);
        }

        #endregion
    }
}
